"""
-*- coding: utf-8 -*-
@Author: li
@Email: lijianqiao2906@live.com
@FileName: middleware.py
@DateTime: 2025/03/08 04:40:00
@Docs: 应用程序中间件
"""

import time
import uuid
from collections import defaultdict
from collections.abc import Callable
from typing import cast

from fastapi import BackgroundTasks, FastAPI, HTTPException, Request, Response, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.types import ASGIApp

from app.core.config import settings
from app.models import User
from app.schemas import OperationLogCreate
from app.services.system import operation_log_service
from app.utils.logger import logger

# 需要从审计日志中排除的路径
EXCLUDED_PATHS = {
    f"{settings.API_PREFIX}/docs",
    f"{settings.API_PREFIX}/openapi.json",
    f"{settings.API_PREFIX}/redoc",
    "/favicon.ico",
    settings.METRICS_PATH,
}


# 简单的内存限流器
class RateLimiter:
    """简单的内存限流器"""

    def __init__(self):
        self._requests: dict[str, list[float]] = defaultdict(list)

    def is_allowed(self, client_ip: str, limit: int = 60, window: int = 60) -> bool:
        """检查是否允许请求"""
        now = time.time()
        window_start = now - window

        # 清理过期的记录
        self._requests[client_ip] = [req_time for req_time in self._requests[client_ip] if req_time > window_start]

        # 检查是否超出限制
        if len(self._requests[client_ip]) >= limit:
            return False

        # 记录本次请求
        self._requests[client_ip].append(now)
        return True


# 全局限流器实例
rate_limiter = RateLimiter()


class RateLimitMiddleware(BaseHTTPMiddleware):
    """限流中间件"""

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """处理限流逻辑"""
        client_ip = request.client.host if request.client else "unknown"

        if not rate_limiter.is_allowed(client_ip, settings.RATE_LIMIT_PER_MINUTE):
            logger.warning(f"Rate limit exceeded for IP: {client_ip}")
            raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="请求过于频繁，请稍后再试")

        return await call_next(request)


class AuditLogMiddleware(BaseHTTPMiddleware):
    """审计日志中间件，自动记录操作日志"""

    def __init__(self, app: ASGIApp):
        super().__init__(app)
        self.log_service = operation_log_service

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        # 如果路径在排除列表中，则直接跳过日志记录
        if request.url.path in EXCLUDED_PATHS:
            return await call_next(request)

        start_time = time.time()
        response = await call_next(request)
        process_time = int((time.time() - start_time) * 1000)  # 转换为毫秒

        # 尝试获取当前用户
        current_user: User | None = None
        try:
            token = request.headers.get("Authorization", "").replace("Bearer ", "")
            if token:
                from app.services.user import user_service
                from app.utils.security import decode_access_token

                payload = decode_access_token(token)
                user_id_str = payload.get("sub") if payload else None
                if user_id_str:
                    user_model = await user_service.get_by_id(int(user_id_str))
                    if user_model:
                        current_user = cast(User, user_model)
        except Exception:
            current_user = None  # 获取用户失败，日志中记录为匿名

        # 只有在成功响应且有端点信息时才记录
        endpoint = request.scope.get("endpoint")
        if response.status_code < 400 and endpoint:
            log_data = OperationLogCreate(
                user_id=current_user.id if current_user else None,
                module=endpoint.__module__,
                action=endpoint.__name__,
                resource_id=request.path_params.get("id"),
                method=request.method,
                path=request.url.path,
                ip_address=request.client.host if request.client else "unknown",
                user_agent=request.headers.get("user-agent", ""),
                response_code=response.status_code,
                response_time=process_time,
            )

            # 正确地将后台任务附加到响应中
            if not hasattr(response, "background") or response.background is None:
                response.background = BackgroundTasks()

            background_tasks = cast(BackgroundTasks, response.background)
            background_tasks.add_task(self.log_service.create, **log_data.model_dump())

        return response


class RequestLoggerMiddleware(BaseHTTPMiddleware):
    """请求日志中间件"""

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """处理请求并记录日志

        Args:
            request (Request): 请求对象
            call_next (Callable): 下一个中间件

        Returns:
            Response: 响应对象
        """
        # 记录请求开始时间
        start_time = time.time()

        # 获取请求信息
        # 获取或生成请求ID
        request_id = request.headers.get("X-Request-ID", str(uuid.uuid4())[:8])
        method = request.method
        url = str(request.url)
        client_host = request.client.host if request.client else ""

        # 记录请求日志
        if settings.ENABLE_REQUEST_TRACKING:
            logger.info(f"Request started: {method} {url} from {client_host} [ID: {request_id}]")

        try:
            # 处理请求
            response = await call_next(request)

            # 计算处理时间
            process_time = time.time() - start_time

            # 记录响应日志
            if settings.ENABLE_REQUEST_TRACKING:
                logger.info(
                    f"Request completed: {method} {url} [ID: {request_id}] "
                    f"- Status: {response.status_code} - Time: {process_time:.4f}s"
                )

            # 添加处理时间到响应头
            response.headers["X-Process-Time"] = str(process_time)
            response.headers["X-Request-ID"] = request_id

            # 记录监控指标
            if settings.ENABLE_PERFORMANCE_MONITORING:
                try:
                    from app.utils.metrics import metrics_collector

                    metrics_collector.record_request(method, request.url.path, response.status_code, process_time)
                except ImportError:
                    pass

            return response
        except Exception as e:
            # 记录错误日志
            logger.error(f"Request failed: {method} {url} [ID: {request_id}] - Error: {str(e)}")
            # 重新抛出异常，让异常处理中间件处理
            raise


def setup_middlewares(app: FastAPI) -> None:
    """设置中间件

    Args:
        app (FastAPI): FastAPI应用实例
    """
    # 安全中间件（生产环境）
    if settings.IS_PRODUCTION and settings.ENABLE_TRUSTED_HOST and settings.ALLOWED_HOSTS:
        app.add_middleware(TrustedHostMiddleware, allowed_hosts=settings.ALLOWED_HOSTS)

    if settings.IS_PRODUCTION and settings.ENABLE_HTTPS_REDIRECT:
        app.add_middleware(HTTPSRedirectMiddleware)

    # CORS中间件
    if settings.BACKEND_CORS_ORIGINS:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
            allow_credentials=True,
            allow_methods=["*"],  # 生产环境中建议指定具体方法，例如: ["GET", "POST", "PUT", "DELETE"]
            allow_headers=["*"],  # 生产环境中建议指定具体头部，例如: ["Content-Type", "Authorization"]
        )

    # 限流中间件
    app.add_middleware(RateLimitMiddleware)

    # Gzip压缩中间件
    if settings.ENABLE_GZIP:
        app.add_middleware(GZipMiddleware, minimum_size=settings.GZIP_MINIMUM_SIZE)

    # 审计日志中间件，应放在请求日志之前，以便获取更准确的端点信息
    app.add_middleware(AuditLogMiddleware)

    # 请求日志中间件
    app.add_middleware(RequestLoggerMiddleware)
